﻿/*
# -*- coding: utf-8 -*-
# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

# 注意：

# 若希望将本代码用于商业用途，需先联系作者并获得明确授权。
# 作者不对因使用本代码引发的任何直接或间接损失负责。
# 如有违反上述条款的行为，作者保留追究法律责任的权利。

# 免责声明：
# 请尊重知识产权，遵循相关法律法规。

# Notice:
# If you wish to use this code for commercial purposes, you must contact the author and obtain explicit authorization.
# The author is not responsible for any direct or indirect losses caused by the use of this code.
# If any of the above terms are violated, the author reserves the right to pursue legal action.
# Disclaimer:
# Please respect intellectual property rights and comply with relevant laws and regulations.

# Внимание:
# Если вы хотите использовать этот код в коммерческих целях, вам необходимо связаться с автором и получить явное разрешение.
# Автор не несет ответственности за любые прямые или косвенные убытки, возникшие в результате использования этого кода.
# В случае нарушения вышеуказанных условий автор оставляет за собой право на юридическое преследование.
# Отказ от ответственности:
# Пожалуйста, уважайте интеллектуальную собственность и соблюдайте соответствующие законы и нормативные акты.

# Attention :
# Si vous souhaitez utiliser ce code à des fins commerciales, vous devez contacter l'auteur et obtenir une autorisation explicite.
# L'auteur n'est pas responsable des pertes directes ou indirectes causées par l'utilisation de ce code.
# En cas de violation des conditions ci-dessus, l'auteur se réserve le droit de poursuivre une action en justice.
# Avertissement :
# Veuillez respecter les droits de propriété intellectuelle et vous conformer aux lois et réglementations en vigueur.

 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;

public class GameBoard : Board
{
    public const int HintsCountForIndex = 3;

    public event Action GameResulted;

    [SerializeField] private AudioClip _successClip, _clickClip;
    [SerializeField] private GameObject _winEffect;
    [SerializeField] private PolyLine _linePrefab;

    private bool? _result;


    public bool Dirty { get; set; }

    public bool Completed
    {
        get => _completed;
        set
        {
            if (value == _completed)
                return;

            _completed = value;

            if (value)
            {
                GameResulted?.Invoke();
            }
        }
    }
    public bool Dragging { get;private set; }
    public  Color LineColor { get; set; }

    public bool HaveUndo => !Completed && _currentLinePoints.Count > 1;

    private bool _completed;
    private PolyLine _bgLine;

    public ILevel Level { get; set; } 
    public float Spacing => _spacing;
    public int HintIndex { get; private set; }
    public bool CanShowHintFurther => HintIndex * HintsCountForIndex < Level.Line.Count();
    private readonly List<Piece> _currentLinePoints = new List<Piece>();
    private PolyLine _line;


    protected override void Awake()
    {
        base.Awake();
        Refresh();
    }


    public void AddHighlightCoordinates(IEnumerable<Vector2Int> coordinates)
    {
        foreach (var coordinate in coordinates)
        {
            this[coordinate].Highlight = true;
        }
    }

    public void ClearHighlights()
    {
        BoardTiles
            .Where(tile => tile.Highlight)
            .ForEach(tile => tile.Highlight = false);
    }


    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (
#if UNITY_EDITOR
                EventSystem.current.IsPointerOverGameObject()
#else
                EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)
#endif
            )
                return;

            Dragging = true;
            // ReSharper disable once Unity.PreferNonAllocApi
            var piece = Physics2D.OverlapPointAll(Camera.main.ScreenToWorldPoint(Input.mousePosition))
                .FirstOrDefault(c => c.GetComponent<Piece>() != null)?.GetComponent<Piece>();

            if (piece != null)
            {
                if (_currentLinePoints.Count > 0)
                {
                    if (!HasAlreadyLine(piece, _currentLinePoints.Last()) &&
                        LevelHasLine(piece, _currentLinePoints.Last()))
                    {
                        ConnectToPiece(piece);

                    }
                    else
                    {
                        //TODO:Vibrate
                    }
                }
                else
                {
                    _currentLinePoints.Add(piece);
                    UpdateLineRenderer();
                    UpdateHighlightPiece();
                }
            }
        }

        if (Dragging)
        {
            // ReSharper disable once Unity.PreferNonAllocApi
            var piece = Physics2D.OverlapPointAll(Camera.main.ScreenToWorldPoint(Input.mousePosition))
                .FirstOrDefault(c => c.GetComponent<Piece>() != null)?.GetComponent<Piece>();

            if (piece != null)
            {
                if (_currentLinePoints.Count > 0)
                {
                    if (!HasAlreadyLine(piece, _currentLinePoints.Last()) &&
                        LevelHasLine(piece, _currentLinePoints.Last()))
                    {
                        ConnectToPiece(piece);

                    }
                    else
                    {
                        //TODO:Vibrate
                    }
                }


            }

            UpdateLineRenderer();
        }

        if (Input.GetMouseButtonUp(0) && Dragging)
            {
                if (_currentLinePoints.Count == 1)
                {
                    _currentLinePoints.Clear();
                }

                Dragging = false;
                UpdateLineRenderer();
                UpdateHighlightPiece();
            }
        
    }

    private void ConnectToPiece(Piece piece)
    {
        _currentLinePoints.Add(piece);
       
        UpdateLineRenderer();
        UpdateHighlightPiece();
       if(IsLevelCompleteConditionSatisfy())
       {
           CompleteTheGameIfCan();
       }
       else
       {
           piece.ShowConnectEffect();
       }
    }

    private void UpdateHighlightPiece()
    {
        Pieces.ForEach(p => p.Highlight = !Completed && p == _currentLinePoints.LastOrDefault());
    }


    private void UpdateLineRenderer()
    {
        if (_currentLinePoints.Count == 0)
        {
            if (_line)
                Destroy(_line.gameObject);
            return;
        }

        if (_line == null)
        {
            _line = Instantiate(_linePrefab);
            _line.Color = LineColor;
        }


        _line.Points =

#if UNITY_EDITOR
            !EventSystem.current.IsPointerOverGameObject()
#else
                !EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)           
#endif
            && !Completed&&Dragging
            ? _currentLinePoints.Select(piece => (Vector2) piece.transform.position)
                .Append((Vector2) Camera.main.ScreenToWorldPoint(Input.mousePosition))
            : _currentLinePoints.Select(piece => (Vector2) piece.transform.position);
    }

    public void Undo()
    {
        if (!HaveUndo || Dragging) return;
        
        _currentLinePoints.RemoveAtEnd();
        
        if(_currentLinePoints.Count<=1)
            _currentLinePoints.Clear();
        
        UpdateLineRenderer();
        UpdateHighlightPiece();
    }

    private void CompleteTheGameIfCan()
    {
        if (!IsLevelCompleteConditionSatisfy())
        {
            return;
        }
        Completed = true;
        HintIndex = 0;
        UpdateHighlightPiece();
        ShowCompleteEffects();
    }

    private bool IsLevelCompleteConditionSatisfy()
    {
        return !Completed && Level.Line.Count() <= _currentLinePoints.Count;
    }

    private void ShowCompleteEffects()
    {
        StartCoroutine(CompleteAnim());
    }

    private IEnumerator CompleteAnim()
    {
        foreach (var piece in _currentLinePoints.Distinct())
        {
            piece.ShowCompleteEffect();
        }

        yield return new WaitForSeconds(0.2f);
        //Handheld.Vibrate();
        PlayClipIfCan(_successClip);

    }

    public void ShowHints()
    {
        if (!CanShowHintFurther)
        {
            return;
        }
        for (int i = HintIndex*HintsCountForIndex; i < (HintIndex+1)*HintsCountForIndex && Level.Line.Count()>i; i++)
        {
            var piece = (Piece)this[Level.Points.ElementAt(Level.Line.ElementAt(i))].Tile;
            piece.AddHintNumber(i+1);
        }
        HintIndex++;

    }


    private void PlayClipIfCan(AudioClip clip, float vol = 0.35f)
    {
        if (AudioManager.IsSoundEnable && clip != null)
        {
            // ReSharper disable once PossibleNullReferenceException
            AudioSource.PlayClipAtPoint(clip, Camera.main.transform.position, vol);
        }
    }

    public void Setup(ILevel level)
    {
        ResetBoard();
        Level = level;
        _bgLine = _bgLine == null ? Instantiate(_linePrefab) : _bgLine;
        _bgLine.Color = Color.gray.WithAlpha(0.3f);

        var list = level.Points.ToList();
        list.ForEach(vec => AddPiece(new PieceData {coordinate = vec}));

        _bgLine.Points = level.Line.Select(i => (Vector2) this[list[i]].transform.position);
    }


    private bool HasAlreadyLine(Piece firstPiece, Piece secondPiece)
    {
        foreach (var index in _currentLinePoints.Select((piece, i) => piece==firstPiece?i:-1).Where(i=>i>=0))
        {
            if (_currentLinePoints.Count > 1 &&
                (index < _currentLinePoints.Count - 1 && _currentLinePoints[index + 1] == secondPiece ||
                 index > 0 && _currentLinePoints[index - 1] == secondPiece))
            {
                return true;
            }
        }

        return false;
    }

    private bool LevelHasLine(Piece firstPiece, Piece lastPiece)
    {
        var line = Level.Points.Select(i => (Piece) this[i].Tile).ToList();

        var list = Level.Line.ToList();

        for (var i = 0; i < list.Count - 1; i++)
        {
            if (line[list[i]] == firstPiece && line[list[i + 1]] == lastPiece ||
                line[list[i]] == lastPiece && line[list[i + 1]] == firstPiece)
            {
                return true;
            }
        }

        return false;
    }


    public override void ResetBoard()
    {
        base.ResetBoard();
        transform.localScale = Vector3.one;
        BoardTiles.ForEach(tile => tile.Highlight = false);
        Completed = false;
        HintIndex = 0;
        if (_bgLine)
        {
            Destroy(_bgLine.gameObject);
            _bgLine = null;
        }

        if (_line)
        {
            Destroy(_line.gameObject);
            _line = null;
        }

        _currentLinePoints.Clear();
        Dirty = false;
    }
}


[Serializable]
public struct PieceData
{
    public Vector2Int coordinate;

    public override bool Equals(object obj)
    {
        if (!(obj is PieceData))
        {
            return false;
        }

        var data = (PieceData) obj;

        return coordinate == data.coordinate;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}